package com.seanzx.dp;

import com.seanzx.common.Lists;
import com.seanzx.common.Result;
import com.seanzx.common.ResultCode;
import com.seanzx.common.StringUtils;
import com.seanzx.dp.condition.ConditionNode;
import com.seanzx.dp.condition.ConditionTree;
import com.seanzx.dp.condition.ConditionType;
import com.seanzx.dp.entity.*;
import com.seanzx.dp.memory.TableBuffer;
import com.seanzx.dp.common.DBProperties;
import com.seanzx.dp.processor.DataProcessor;
import com.seanzx.dp.processor.DatabaseProcessor;
import com.seanzx.dp.processor.TableProcessor;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class ProcessorController {
    private static final ProcessorController SINGLETON = new ProcessorController();

    private final DatabaseProcessor databaseProcessor = new DatabaseProcessor();
    private final TableProcessor tableProcessor = new TableProcessor();
    private final DataProcessor dataProcessor = new DataProcessor();
    private static String CURRENT_DATABASE = "information_schema";


    public Result<?> createDatabase(String name) {
        try {
            return databaseProcessor.create(name);
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION);
        }
    }

    public Result<?> dropDatabase(String name) {
        try {
            if (CURRENT_DATABASE.equals(name)) {
                return Result.of(ResultCode.CANNOT_DROP_CURRENT_DATABASE);
            }
            return databaseProcessor.drop(name);
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION);
        }
    }

    public Result<List<String>> showDatabase() {
        return databaseProcessor.showDatabase();
    }

    public Result<?> useDatabase(String name) {
        try {
            Result<Boolean> exists = databaseProcessor.exists(name);
            if (!exists.getData()) {
                return Result.of(ResultCode.DATABASE_NOT_EXISTS);
            }
            CURRENT_DATABASE = name;
            return Result.ok();
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION);
        }
    }

    public Result<Integer> createTable(Table table) {
        table.setTableSchema(CURRENT_DATABASE);
        for (Column column : table.getColumns()) {
            column.setTableSchema(CURRENT_DATABASE);
        }
        return createTable(CURRENT_DATABASE, table);
    }

    public Result<Integer> createTable(String databaseName, Table table) {
        try {
            Result<Integer> result = tableProcessor.create(databaseName, table.getName());
            if (!result.isSuccess()) {
                return Result.of(result.getResultCode(), 0);
            }
            List<Column> tableColumns = TableBuffer.getTable("information_schema", "TABLES").get().getColumns();
            result = insertTableConfiguration(table, tableColumns);
            if (!result.isSuccess()) {
                return result;
            }
            List<Column> columnColumns = TableBuffer.getTable("information_schema", "COLUMNS").get().getColumns();
            for (int i = 0; i < table.getColumns().size(); i++) {
                Column column = table.getColumns().get(i);
                result = insertColumnConfiguration(column, columnColumns);
                if (!result.isSuccess()) {
                    return result;
                }
            }
            TableBuffer.addTable(table);
            return Result.ok(1);
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION, 0);
        }
    }

    public Result<Integer> dropTable(DropTableEntity entity) {
        String databaseName = entity.getDatabaseName();
        if (StringUtils.isEmpty(databaseName)) {
            databaseName = CURRENT_DATABASE;
        }
        String tableName = entity.getTableName();
        return dropTable(databaseName, tableName);
    }

    public Result<Integer> dropTable(String databaseName, String tableName) {
        try {
            Result<Integer> result = tableProcessor.drop(databaseName, tableName);
            if (!result.isSuccess()) {
                return result;
            }
            ConditionTree tableTree = new ConditionTree("information_schema", "TABLES");
            tableTree.add(new ConditionNode("TABLE_SCHEMA", ConditionType.EQUALS, Lists.of(databaseName)));
            tableTree.add(new ConditionNode("TABLE_NAME", ConditionType.EQUALS, Lists.of(tableName)));
            result = dataProcessor.delete("information_schema", "TABLES", tableTree);
            if (ResultCode.TABLE_NOT_EXISTS.equals(result.getResultCode())) {
                return Result.of(ResultCode.TABLES_CONFIGURATION_MISSING, 0);
            }
            if (!result.isSuccess()) {
                return result;
            }
            ConditionTree columnTree = new ConditionTree("information_schema", "COLUMNS");
            columnTree.add(new ConditionNode("TABLE_SCHEMA", ConditionType.EQUALS, Lists.of(databaseName)));
            columnTree.add(new ConditionNode("TABLE_NAME", ConditionType.EQUALS, Lists.of(tableName)));
            result = dataProcessor.delete("information_schema", "COLUMNS", columnTree);
            if (ResultCode.TABLE_NOT_EXISTS.equals(result.getResultCode())) {
                return Result.of(ResultCode.COLUMNS_CONFIGURATION_MISSING, 0);
            }
            return result;
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION, 0);
        }
    }

    public Result<List<List<String>>> select(DataSelectEntity entity) {
        try {
            if (entity.getDatabaseName() == null) {
                entity.setDatabaseName(CURRENT_DATABASE);
            }
            Optional<Table> optional = TableBuffer.getTable(entity.getDatabaseName(), entity.getTableName());
            if (!optional.isPresent()) {
                return Result.of(ResultCode.TABLE_NOT_EXISTS, null);
            }
            Table table = optional.get();
            List<Column> columns = table.getColumns();
            Result<List<List<String>>> result;
            List<String> columnNames;

            if (entity.getColumns().size() == 1 && "*".equals(entity.getColumns().get(0))) {
                result = dataProcessor.select(entity.getDatabaseName(), entity.getTableName(),
                        columns, entity.getConditionTree());
                columnNames = table.getColumns().stream().map(Column::getName).collect(Collectors.toList());
            } else {
                result = dataProcessor.select(entity.getDatabaseName(), entity.getTableName(),
                        columns, entity.getColumns(), entity.getConditionTree());
                columnNames = table.getColumns().stream().map(Column::getName)
                        .filter(name -> entity.getColumns().contains(name)).collect(Collectors.toList());            }
            if (!result.isSuccess()) {
                return result;
            }
            result.getData().add(0, columnNames);
            result.serializeBy((l) -> {
                StringBuilder builder = new StringBuilder();
                for (List<String> strings : l) {
                    for (String string : strings) {
                        builder.append(string).append("\t\t\t");
                    }
                    builder.append("\n");
                }
                return builder.toString();
            });
            return result;
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION, null);
        }
    }

    public Result<List<List<String>>> select(String databaseName, String tableName,List<Column> columns, ConditionTree conditionTree) {
        try {
            return dataProcessor.select(databaseName, tableName, columns, conditionTree);
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION, null);
        }
    }

    public Result<Integer> delete(DataOperateEntity dataOperateEntity) {
        try {
            if (dataOperateEntity.getDatabaseName() == null) {
                dataOperateEntity.setDatabaseName(CURRENT_DATABASE);
            }
            return dataProcessor.delete(dataOperateEntity.getDatabaseName(), dataOperateEntity.getTableName(), dataOperateEntity.getConditionTree());
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION, 0);
        }
    }

    public Result<Set<String>> properties() {
        return DBProperties.names();
    }

    public Result<Set<String>> properties(String key) {
        return DBProperties.names(key);
    }

    public Result<?> getProperties(String key) {
        Result<?> result = DBProperties.get(key);
        if (result.isSuccess()) {
            String property = (String) result.getData();
            List<List<String>> results = Lists.of(
                    Lists.of("KEY", "VARIABLE"),
                    Lists.of(key, property)
            );
            return Result.ok(results);
        }
        return result;
    }

    public Result<?> setProperties(String key, String value) {
        return DBProperties.set(key, value);
    }

    public String getDataBaseName(String databaseName) {
        if (StringUtils.isEmpty(databaseName)) {
            return CURRENT_DATABASE;
        }
        return databaseName;
    }

    public Result<Integer> insert(DataInsertEntity dataInsertEntity) {
        try {
            if (dataInsertEntity.getDatabaseName() == null) {
                dataInsertEntity.setDatabaseName(CURRENT_DATABASE);
            }
            int rows = 0;
            Optional<Table> optional = TableBuffer.getTable(dataInsertEntity.getDatabaseName(), dataInsertEntity.getTableName());
            if (!optional.isPresent()) {
                return Result.of(ResultCode.TABLE_NOT_EXISTS, 0);
            }
            Table table = optional.get();
            List<Column> columns = table.getColumns();
            if (dataInsertEntity.getColumns() != null && !dataInsertEntity.getColumns().isEmpty()) {
                for (int i = 0; i < columns.size(); i++) {
                    Column column = columns.get(i);
                    if (dataInsertEntity.getColumns().contains(column.getName())) {
                        continue;
                    }
                    for (List<String> row : dataInsertEntity.getRows()) {
                        row.add(i, column.getDefaultValue());
                    }
                }
            }
            for (List<String> row : dataInsertEntity.getRows()) {
                Result<Integer> insertResult = dataProcessor.insert(dataInsertEntity.getDatabaseName(),
                        dataInsertEntity.getTableName(), columns, row);
                if (!insertResult.isSuccess()) {
                    return insertResult;
                }
                rows += insertResult.getData();
            }
            Result<Integer> successResult = Result.ok(rows);
            successResult.serializeBy((i) -> "insert success, total row count is " + i);
            return successResult;
        } catch (IOException e) {
            return Result.of(ResultCode.IO_EXCEPTION, 0);
        }
    }

    public static ProcessorController get() {
        return SINGLETON;
    }

    private ProcessorController(){

    }

    public Result<Integer> insertTableConfiguration(Table table, List<Column> tableColumns) throws IOException{
        return dataProcessor.insert("information_schema", "TABLES", tableColumns, Lists.of(table.getTableSchema(), table.getName()));
    }

    public Result<Integer> insertColumnConfiguration(Column column, List<Column> columnColumns) throws IOException {
        return dataProcessor.insert("information_schema", "COLUMNS", columnColumns,
                Lists.of(column.getTableSchema(), column.getTableName(), column.getName(),
                        column.getType().toString(), String.valueOf(column.getLength()),
                        String.valueOf(column.getIndex())));
    }

    public TableProcessor getTableProcessor() {
        return tableProcessor;
    }
}
